package com.ruoyi.sqlbuilder.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ZipUtil;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.entity.SysDictData;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.DictUtils;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.sqlbuilder.domain.Columns;
import com.ruoyi.sqlbuilder.domain.SbrDatabase;
import com.ruoyi.sqlbuilder.domain.SbrExpDbInfo;
import com.ruoyi.sqlbuilder.execute.constants.ConvertConstants;
import com.ruoyi.sqlbuilder.execute.convert.ISqlHandler;
import com.ruoyi.sqlbuilder.execute.factory.SqlExecuteFactory;
import com.ruoyi.sqlbuilder.service.IConvertSqlService;
import com.ruoyi.sqlbuilder.service.ISbrDatabaseService;
import com.ruoyi.sqlbuilder.service.ISbrExpDbInfoService;
import com.ruoyi.sqlbuilder.vo.SqlHandleVo;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;

/**
 * @ClassName: ConvertSqlServiceImpl
 * @Description:
 * @Author: Yanzy
 * @Data: 2021/5/11 17:21
 * @Source: 1.0
 */
@Service
public class ConvertSqlServiceImpl implements IConvertSqlService {

    private Logger logger = LoggerFactory.getLogger(ConvertSqlServiceImpl.class);

    @Autowired
    private ISbrDatabaseService sbrDatabaseService;
    @Autowired
    private ISbrExpDbInfoService sbrExpDbInfoService;

    @Override
    public String execute(SbrExpDbInfo sbrExpDbInfo) {

        String filePath = StringUtils.EMPTY;
        try {
            Assert.notNull(sbrExpDbInfo, "导出配置信息为空...");
            // 获取导出配置
            SbrDatabase sbrDatabase = sbrDatabaseService.selectSbrDatabaseById(sbrExpDbInfo.getDbId());
            SqlHandleVo vo = new SqlHandleVo(sbrDatabase);
            vo.setExportType(sbrExpDbInfo.getDbType());
            vo.setTableSchema(sbrExpDbInfo.getTableSchema());
            vo.setExportData(StringUtils.equals("Y", sbrExpDbInfo.getExportDataFlag()));
            filePath = execute(vo);
        } catch (Exception e) {
            logger.error("导出SQL出现异常....");
            e.printStackTrace();
        } finally {
            SbrExpDbInfo update = new SbrExpDbInfo();
            update.setId(sbrExpDbInfo.getId());
            update.setExportFlag(StringUtils.isBlank(filePath) ? 1 : 0);
            sbrExpDbInfoService.updateSbrExpDbInfo(update);
        }
        return filePath;
    }

    private String execute(SqlHandleVo vo) throws Exception {

        Assert.notNull(vo, "处理传输对象为空...");
        String filePath = RuoYiConfig.getProfile() + File.separator + "sql" + File.separator + DateUtils.dateTimeNow();
        // 判断导出需求
        if (StringUtils.equalsIgnoreCase(ConvertConstants.DB_ALL, vo.getExportType())) {
            // 获取字典缓存
            List<SysDictData> dictCache = DictUtils.getDictCache("sbr_database_type");
            Assert.notNull(dictCache, String.format("获取 {{ %s }} 字典为空", "sbr_database_type"));
            // 导出需求全部
            CountDownLatch count = new CountDownLatch(dictCache.size() - 1);
            ExecutorService executor = Executors.newFixedThreadPool(5);
            /*new ThreadPoolExecutor(3, 10, 1,
                    TimeUnit.MINUTES, new LinkedBlockingDeque<>(), r -> new Thread("convert-sql-thread-" + Math.floor(Math.random() * 10)));*/
            for (SysDictData dict : dictCache) {
                if (StringUtils.equalsIgnoreCase(ConvertConstants.DB_ALL, dict.getDictValue())) {
                    continue;
                }
                executor.execute(new BranchRun(count, filePath, vo, dict.getDictValue(), this));
            }
            count.await();
            executor.shutdown();
        } else {
            execute(filePath, vo, vo.getExportType());
        }
        File zip = ZipUtil.zip(filePath);
        // 压缩完成删除缓存文件
        FileUtil.del(filePath);
        return zip.getAbsolutePath();
    }

    private void execute(String filePath, SqlHandleVo vo, String exportType) {
        // 在 filePath 目录下生成exportType.sql文件
        String fileName = exportType + ".sql";
        String cacheFile = filePath + File.separator + fileName;
        // 设置插入sql数据临时文件
        String tmpPath = filePath + File.separator + "temp_" + exportType + DateUtils.dateTimeNow() + ".sql";
        List<String> sqlData = new ArrayList<>();
        try {
            // 创建导出实例
            ISqlHandler sqlHandler = SqlExecuteFactory.invoke(exportType);
            // 表sql
            List<String> tables = sqlHandler.selectTables(vo);
            for (String table : tables) {
                List<Columns> columnsList = sqlHandler.selectTableColumns(vo, table);
                // 表信息
                // 特殊处理操作
                String tableSpecial = sqlHandler.createTableSpecial(columnsList);
                if (StringUtils.isNotBlank(tableSpecial)) {
                    sqlData.add(tableSpecial);
                }
                String tableHead = sqlHandler.createTableHead(table, vo.getTableSchema());
                String tableBody = sqlHandler.createTableBody(columnsList);
                sqlData.add(String.format(tableHead, tableBody));
                // 增加换行符
                sqlData.add("\r\n");

                // #TODO 处理外键
                List<String> foreignKeySql = sqlHandler.selectAndHandleForeignKey(vo, table);
                if (CollectionUtils.isNotEmpty(foreignKeySql)) {
                    sqlData.addAll(foreignKeySql);
                    sqlData.add("\r\n");
                }
                // #TODO 处理索引
                List<String> indexSql = sqlHandler.selectAndHandleIndex(vo, table);
                if (CollectionUtils.isNotEmpty(columnsList)) {
                    sqlData.addAll(indexSql);
                    sqlData.add("\r\n");
                }
                // 数据信息
                // #TODO 可在此处增加 导出数据开关, 也可增加导出数据表过滤条件
                if (vo.isExportData()) {
                    sqlHandler.selectTableValues(columnsList, vo, table, tmpPath);
                }
            }
            FileUtil.appendLines(sqlData, cacheFile, Constants.UTF8);
            // 数据写入后清空
            sqlData.clear();
            // 合并insert数据
            File tmp = FileUtil.file(tmpPath);
            FileUtils.union(tmp, FileUtil.file(cacheFile), Constants.UTF8);
        } catch (IOException e) {
            logger.error("导出sql文件程序执行出错: {}", e.getMessage());
            e.printStackTrace();
        }
    }

    private static class BranchRun implements Runnable {

        private Logger logger = LoggerFactory.getLogger(BranchRun.class);

        private ConvertSqlServiceImpl convertSqlService;

        private CountDownLatch count;

        private String filePath;

        private SqlHandleVo vo;

        private String exportType;

        public BranchRun(CountDownLatch count, String filePath, SqlHandleVo vo, String exportType, ConvertSqlServiceImpl convertSqlService) {
            this.count = count;
            this.convertSqlService = convertSqlService;
            this.filePath = filePath;
            this.vo = vo;
            this.exportType = exportType;
        }

        @Override
        public void run() {
            try {
                convertSqlService.execute(filePath, vo, exportType);
            } catch (Exception e) {
                logger.error("多线程执行导出任务异常: {}", e.getMessage());
            } finally {
                count.countDown();
            }
        }
    }
}
